package com.mls.guardian.appst.shell;

import com.mls.guardian.ThreadFactoryExetend;
import com.mls.guardian.appst.chain.TaskResult;
import com.mls.guardian.common.OSPlatform;
import com.mls.guardian.common.OSUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * Created by 2014-400 on 2015-10-23.
 */
public class ShellScriptExecutorFuture {
    private static final Logger log = LoggerFactory.getLogger(ShellScriptExecutorFuture.class);
    private static final long STOP_TIMEOUT_ADDITION = 2000L;
    private static final long COMMAND_TIME_OUT = 3 * 60 * 1000L;

    private long timeout = COMMAND_TIME_OUT;
    private Process process = null;
    private boolean isInput = true;
    private boolean isError = false;

    private String oldString = "";
    private String newString = "";
    private String errorString = "";

    private boolean threadInputStop = false;
    private boolean threadOutputStop = false;
    private boolean threadErrorStop = false;

    private List<String> list;
    private int index = 0;
    private long startTime = System.currentTimeMillis();

    /**
     * 可以使单条命令,也可以是一个脚本文件；
     * 注意一定要有输出结果,如果没有输出，可以放到一个脚本文件，人为叫一个输出结果;
     * 如果没有输出结果会导致输出数据线程一直死等,无法输入下一条命令
     *
     * @param list 命令集合
     */
    public ShellScriptExecutorFuture(List<String> list) {
        this.list = list;
        try {
            if (OSUtil.getOSname() == OSPlatform.Windows) {
                this.process = Runtime.getRuntime().exec("cmd");
            } else {
                this.process = Runtime.getRuntime().exec("/bin/bash");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 可以使单条命令,也可以是一个脚本文件；
     * 注意一定要有输出结果,如果没有输出，可以放到一个脚本文件，人为叫一个输出结果;
     * 如果没有输出结果会导致输出数据线程一直死等,无法输入下一条命令
     *
     * @param list 命令集合
     */
    public ShellScriptExecutorFuture(List<String> list,long timeout) {
        this.list = list;
        this.timeout = timeout;
        try {
            if (OSUtil.getOSname() == OSPlatform.Windows) {
                this.process = Runtime.getRuntime().exec("cmd");
            } else {
                this.process = Runtime.getRuntime().exec("/bin/bash");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    /**
     * 传入单个脚本文件
     *
     * @param bashFilePath 脚本文件路径
     */
    public ShellScriptExecutorFuture(String bashFilePath) {
        this.list = new ArrayList<String>();
        this.list.add(bashFilePath);
        this.list.add("exit");

        try {
            if (OSUtil.getOSname() == OSPlatform.Windows) {
                this.process = Runtime.getRuntime().exec("cmd");
            } else {
                this.process = Runtime.getRuntime().exec("/bin/bash");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private TaskResult getMonitor() {
        TaskResult result = null;
        while (true) {
            //其余三个线程全部停止了 才停止监控线程
            if (isThreadInputStop() == true && (isThreadOutputStop() == true || isThreadErrorStop() == true)) {
                result = new TaskResult();
                result.setOut(getNewString());
                result.setError(getErrorString());
                result.setSuc(isError() == true ? false : true);

                log.info("---结束了monitor---" + System.currentTimeMillis());
                //System.out.println("---结束啦monitor---" + System.currentTimeMillis());
                break;
            }

            long endTime = System.currentTimeMillis();
            if (endTime - getStartTime() > this.timeout) {
                result = new TaskResult();
                result.setOut("");
                result.setError("----timeout----");
                result.setSuc(false);
                log.info("---超时了---" + System.currentTimeMillis());
                break;
            }

            try {
                Thread.sleep(10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (isInput == false) {
                checkOutput();
            }
        }

        return result;
    }

    private TaskResult getInput(OutputStream outputStream) {
        TaskResult result = null;
        PrintWriter pw = new PrintWriter(outputStream, true);
        while (true) {
            try {
                Thread.sleep(1 * 1000L);//模拟手动输入命令
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (isInput() == true) {
                String cmd = getNextCmd();
                if (isError() == true) {
                    cmd = "exit";
                }
                //System.out.println("input_cmd:" + cmd);
                log.info("input_cmd:" + cmd + " time is"+System.currentTimeMillis());
                if (cmd.length() == 0 || cmd.equals("exit") || cmd.equals("quit")) {
                    setInput(false);
                    setStartTime(System.currentTimeMillis());
                    // System.out.println("---结束啦input---" + System.currentTimeMillis());
                    break;
                } else {
                    setInput(false);
                    setStartTime(System.currentTimeMillis());
                    pw.println(cmd);
                }
            }
        }
        log.info("---结束了input---" + System.currentTimeMillis());
        setThreadInputStop(true);
        pw.close();

        return result;
    }

    private TaskResult getOutput(InputStream inputStream) {
        TaskResult result = null;
        InputStreamReader reader = null;
        reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        char[] buf = new char[1024];
        int size;
        while (true) {
            try {
                size = reader.read(buf);
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
            if (size != -1) {
                setStartTime(System.currentTimeMillis());
                setOutput(new String(buf, 0, size));
                log.debug("output:" + new String(buf, 0, size) + System.currentTimeMillis());
                //System.out.print(new String(buf, 0, size));
            } else {
                System.out.println("---结束了output---" + System.currentTimeMillis());
                break;
            }
        }
        log.info("---结束了output---" + System.currentTimeMillis());
        setThreadOutputStop(true);
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private TaskResult getError(InputStream inputStream) {
        TaskResult result = null;
        InputStreamReader reader = null;
        reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        char[] buf = new char[1024];
        int size;
        while (true) {
            try {
                size = reader.read(buf);
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
            if (size != -1) {
                setStartTime(System.currentTimeMillis());
                String errorStr = new String(buf, 0, size);
                setError(true, errorStr);
                setOutput(errorStr);
                log.info("error:" + errorStr + System.currentTimeMillis());
                //System.out.print("error:" + new String(buf, 0, size));
            } else {
                //System.out.println("---结束了Error---" + System.currentTimeMillis());
                break;
            }
        }
        log.info("---结束了Error---" + System.currentTimeMillis());
        setThreadErrorStop(true);
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }


    public TaskResult execute() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactoryExetend("executeShell_"));
        List<Future<TaskResult>> futures = new ArrayList<>();
        //监控
        futures.add(executorService.submit(new Callable<TaskResult>() {
            @Override
            public TaskResult call() throws Exception {
                return getMonitor();
            }
        }));
        //输入
        futures.add(executorService.submit(new Callable<TaskResult>() {
            @Override
            public TaskResult call() throws Exception {
                return getInput(process.getOutputStream());
            }
        }));
        //输出
        futures.add(executorService.submit(new Callable<TaskResult>() {
            @Override
            public TaskResult call() throws Exception {
                return getOutput(process.getInputStream());
            }
        }));
        //错误
        futures.add(executorService.submit(new Callable<TaskResult>() {
            @Override
            public TaskResult call() throws Exception {
                return getError(process.getErrorStream());
            }
        }));


        TaskResult taskResult = futures.get(0).get();

        executorService.shutdownNow();

        return taskResult;
    }

    public long getStartTime() {
        return startTime;
    }

    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    public void setOutput(String msg) {
        newString += msg;
    }

    public void checkOutput() {
        if (oldString.equals(newString) && oldString.length() > 0) {
            setInput(true);
            newString = "";
        } else {
            oldString = newString;
        }
    }

    public String getNextCmd() {
        String rtn = "";
        if (index < list.size()) {
            rtn = list.get(index);
            index++;
        }
        return rtn;
    }

    public String getCurrentCmd() {
        if (index < list.size()) {
            return list.get(index);
        } else {
            return list.get(list.size() - 1);
        }
    }

    public boolean isInput() {
        return isInput;
    }

    public void setInput(boolean isInput) {
        this.isInput = isInput;
    }

    public boolean isError() {
        return this.isError;
    }

    public void setError(boolean isError) {
        this.isError = isError;
    }

    public void setError(boolean isError, String errorString) {
        this.isError = isError;
        if (errorString != null && errorString.length() > 0) {
            this.errorString += errorString;
        }
    }

    public String getErrorString() {
        return this.errorString;
    }

    public void setErrorString(String errorString) {
        this.errorString = errorString;
    }

    public String getOldString() {
        return oldString;
    }

    public void setOldString(String oldString) {
        this.oldString = oldString;
    }

    public String getNewString() {
        return newString;
    }

    public void setNewString(String newString) {
        this.newString = newString;
    }

    public boolean isThreadInputStop() {
        return threadInputStop;
    }

    public void setThreadInputStop(boolean threadInputStop) {
        this.threadInputStop = threadInputStop;
    }

    public boolean isThreadOutputStop() {
        return threadOutputStop;
    }

    public void setThreadOutputStop(boolean threadOutputStop) {
        this.threadOutputStop = threadOutputStop;
    }

    public boolean isThreadErrorStop() {
        return threadErrorStop;
    }

    public void setThreadErrorStop(boolean threadErrorStop) {
        this.threadErrorStop = threadErrorStop;
    }
}
